/*
 * Copyright 2022 The Open Islands Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.openislands.oi.console.config;

import org.openislands.oi.console.config.annotation.CommandGroup;
import org.openislands.oi.console.config.bean.LoadBeanHandler;
import org.openislands.oi.console.exception.InvokeException;
import org.openislands.oi.constant.CommunicationProtocolEnum;
import org.openislands.oi.constant.Dict;
import org.openislands.oi.rpc.base.ExecutorProxy;
import org.openislands.oi.rpc.base.RouteNode;
import org.openislands.oi.util.ConfiguratorUtils;
import org.openislands.oi.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Component
public class ServiceManager implements ApplicationListener<ContextRefreshedEvent>, LoadBeanHandler, PropertySources {
    private static final Logger log = LoggerFactory.getLogger(ServiceManager.class);

    /***
     * Group name, service mark cache
     * Load see {@link #initBean(Map)}
     */
    private static final Map<String, String> serverMap = new HashMap<>();

    /***
     * Communication Protocol Executor Proxy
     * Load see {@link #onApplicationEvent(ContextRefreshedEvent)}
     * see <{@link CommunicationProtocolEnum#getType()}, {@link ExecutorProxy}>
     */
    private static final Map<Class<?>, ExecutorProxy> executorProxyCache = new HashMap<>();

    /***
     * Service mark, route cache
     * Load service route see static {}
     */
    private static final Map<String, RouteNode> serviceRouteCache = new HashMap<>();
    private static final Map<String, PropertySource<?>> propertySources = new HashMap<>();

    public static ExecutorProxy getExecutorProxy() {
        return executorProxyCache.get(CommunicationProtocolEnum.HTTP.getType());
    }

    public static String getServerMark(String groupName) {
        return serverMap.get(groupName);
    }

    public static RouteNode getRoute(String serviceName) {
        RouteNode routeNode = serviceRouteCache.get(serviceName);
        if (Objects.isNull(routeNode)) throw new InvokeException("not found service[" + serviceName + "] route");
        return routeNode;
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        ApplicationContext applicationContext = event.getApplicationContext();
        final Map<String, ExecutorProxy> beansOfType = applicationContext.getBeansOfType(ExecutorProxy.class);
        executorProxyCache.putAll(
                beansOfType.values().stream().collect(Collectors.toMap(ExecutorProxy::getClass, Function.identity()))
        );
    }

    @Override
    public void loadBean(Set<Class<?>> classes, Map<Class<?>, List<Object>> resultMap) {
    }

    /***
     * Load group service
     * @param beanMap bean container
     */
    @Override
    public void initBean(Map<Class<?>, List<Object>> beanMap) {
        Set<Class<?>> classes = beanMap.keySet();
        for (Class<?> aClass : classes) {
            CommandGroup commandGroup = aClass.getAnnotation(CommandGroup.class);
            if (Objects.nonNull(commandGroup)) {
                serverMap.put(commandGroup.value(), commandGroup.service());
            }
        }
    }

    public static void loadConfig() throws IOException {
        String config = System.getProperty(Dict.OI_EXTERNAL_CONFIG);
        String host = System.getProperty(Dict.CLI_OI_APISERVER_HOST);
        String httpPort = System.getProperty(Dict.CLI_OI_APISERVER_HTTP_PORT);
        String grpcPort = System.getProperty(Dict.CLI_OI_APISERVER_GRPC_PORT);

        // load application
        Properties properties = ConfiguratorUtils.getProperties();
        YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
        ClassPathResource pathResource = new ClassPathResource(Dict.APPLICATION_CONFIG_FILE);
        List<PropertySource<?>> sources = loader.load(Dict.APPLICATION_CONFIG_FILE, pathResource);
        propertySources.put(Dict.APPLICATION_CONFIG_FILE, sources.get(0));
        properties.putAll(((OriginTrackedMapPropertySource) sources.get(0)).getSource());

        // load service route
        OriginTrackedMapPropertySource source = null;
        String[] serviceMark = {Dict.APISERVER_MARK, Dict.MASTER_MARK, Dict.WORKER_MARK};
        String[] propertySuffix = {".host", ".http_port", ".grpc_port"};
        String[][] routeArray = {{host, httpPort, grpcPort}, {null, null, null}};
        for (int markIndex = 0; markIndex < serviceMark.length; markIndex++) {
            try {
                String mark = serviceMark[markIndex];
                // load oi config
                if (StringUtils.anyNull(routeArray[markIndex])) {
                    if (source == null) {
                        Resource resource = new FileSystemResource(config + "/" + Dict.COMMON_CONFIG_FILE);
                        if (!resource.exists()) {
                            resource = new FileSystemResource("conf/" + Dict.COMMON_CONFIG_FILE);
                        }
                        List<PropertySource<?>> sourceList = loader.load(Dict.COMMON_CONFIG_FILE, resource);
                        source = ((OriginTrackedMapPropertySource) sourceList.get(0));
                        propertySources.put(Dict.COMMON_CONFIG_FILE, source);
                        properties.putAll(source.getSource());
                    }
                    // get route
                    for (int i = 0; i < propertySuffix.length; i++) {
                        String message = mark + propertySuffix[i] + " is null";
                        routeArray[markIndex][i] = routeArray[markIndex][i] != null ? routeArray[markIndex][i]
                                : Objects.requireNonNull(source.getProperty(mark + propertySuffix[i]), message).toString();
                    }
                }

                RouteNode routeNode = new RouteNode();
                routeNode.setHttpAccess(String.format("%s:%s", routeArray[markIndex][0], routeArray[markIndex][1]));
                routeNode.setGrpcAccess(String.format("%s:%s", routeArray[markIndex][0], routeArray[markIndex][2]));
                serviceRouteCache.put(mark, routeNode);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }

        if (serviceRouteCache.isEmpty()) {
            String message = config != null ? config : "conf/";
            message = message.endsWith("/") ? message : message + "/";
            throw new IOException("Not found config file " + message + Dict.COMMON_CONFIG_FILE + " and all service routes are null");
        }
    }

    @Override
    public boolean contains(String name) {
        return propertySources.containsKey(name);
    }

    @Override
    public PropertySource<?> get(String name) {
        return propertySources.get(name);
    }

    @Override
    public Iterator<PropertySource<?>> iterator() {
        return propertySources.values().iterator();
    }
}
